package runtime_server

import (
	"context"
	"fmt"
	"io"
	"sync"

	"gitee.com/hasika/common-server/conn/inet"
	"gitee.com/hasika/common-server/conn/kcp"
	"gitee.com/hasika/common-server/conn/tcp"
	"gitee.com/hasika/common-server/conn/udp"
	"gitee.com/hasika/common-server/conn/ws"
	"gitee.com/hasika/common-server/idispatcher"
	"gitee.com/hasika/common-server/option"
)

type MultiRoutineServer struct {
	ls            inet.IListener
	ctx           context.Context
	serviceOption *option.ServiceOption
	listenOption  *option.RemoteOption
	dispatcher    idispatcher.MessageDispatcher
	closeOnce     sync.Once
}

func NewMultiRoutineServer(ctx context.Context, serviceOption *option.ServiceOption, listenOption *option.RemoteOption, dispatcher idispatcher.MessageDispatcher) IRuntimeServer {
	return &MultiRoutineServer{serviceOption: serviceOption, listenOption: listenOption, ctx: ctx, dispatcher: dispatcher}
}

func (m *MultiRoutineServer) GetServerOption() *option.ServiceOption {
	return m.serviceOption
}

func (m *MultiRoutineServer) Listen() error {
	ls, err := NewListener(m.ctx, m.listenOption.Network(), fmt.Sprintf("%s:%d", m.listenOption.Host(), m.listenOption.Port()), m.serviceOption)
	if err != nil {
		return err
	}
	m.ls = ls
	return nil
}

func (m *MultiRoutineServer) Run() error {
	defer m.Stop()
	for {
		conn, err := m.ls.Accept()
		if err != nil {
			return err
		}
		if conn == nil {
			continue
		}
		writeCtx := inet.NewWriteToChanCtx()
		conn.InitConnection(conn, m.serviceOption.GetConnectionCallback(), false, writeCtx, m.listenOption.RecordReadWrite())
		go m.ServeConnection(conn)
	}
}

func (m *MultiRoutineServer) Stop() {
	m.closeOnce.Do(func() {
		if m.ls != nil {
			m.ls.Close()
		}
	})
}

func (m *MultiRoutineServer) ServeConnection(conn inet.Conn) {
	defer func() {
		conn.CloseConnectionAllCallback("exit run")
	}()
	ctx, cancel := context.WithCancel(m.ctx)
	defer cancel()
	go m.readLoop(conn, ctx, cancel)
	go m.writeLoop(conn, ctx, cancel)
	select {
	case <-ctx.Done():
		return
	}
}

func (m *MultiRoutineServer) readLoop(conn inet.Conn, ctx context.Context, cancel context.CancelFunc) {
	slicer := m.listenOption.GetFrameReader()
	coder := m.serviceOption.GetCoder()
	defer cancel()
	for {
		select {
		case <-ctx.Done():
			Log.Infof("read loop end")
			return
		default:
			packBytes, err := slicer.BlockSlicePacketBytes(conn)
			if err != nil {
				if err != io.EOF {
					Log.Warnf("read err %+v", err)
				}
				return
			}
			packet, err := coder.Unmarshal(packBytes)
			if err != nil {
				Log.Errorf("unmarshal err %+v", err)
				continue
			}
			m.dispatcher.DispatchMessage(packet, conn)
		}
	}
}

func (m *MultiRoutineServer) writeLoop(conn inet.Conn, ctx context.Context, cancel context.CancelFunc) {
	writeCtx := conn.GetWriteCtx()
	writeCh := writeCtx.(*inet.WriteToChanCtx).Buffer()
	rw := m.listenOption.GetFrameReader()
	defer cancel()
	for {
		select {
		case <-ctx.Done():
			Log.Infof("write loop end")
			return
		case data := <-writeCh:
			err := conn.LowerWrite(data.Bytes())
			if err != nil {
				if err != io.EOF {
					Log.Warnf("write err %+v", err)
				}
				rw.Return(data)
				return
			}
			rw.Return(data)
		}
	}
}

func NewListener(ctx context.Context, network inet.ConnectionType, address string, option *option.ServiceOption) (inet.IListener, error) {
	switch network {
	case inet.Tcp:
		return tcp.NewTcpListener(address, option)
	case inet.Udp:
		return udp.NewServerListener(address, option)
	case inet.Kcp:
		return kcp.NewKcpListener(address, option)
	case inet.Websocket:
		return ws.NewWebsocketListener(ctx, address, option)
	}
	panic("not implement")
}
